#! /bin/sh
#
# /etc/init.d/openflow-switch
#
# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
# Modified for Debian by Ian Murdock <imurdock@gnu.ai.mit.edu>.
# Further changes by Javier Fernandez-Sanguino <jfs@debian.org>
# Modified for openflow-switch.
#
# Version:	@(#)skeleton  1.9  26-Feb-2001  miquels@cistron.nl
#
### BEGIN INIT INFO
# Provides:          openflow-switch
# Required-Start:    $network $named $remote_fs $syslog
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: OpenFlow switch
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/secchan
NAME=secchan
DESC=secchan

test -x $DAEMON || exit 0

NICIRA_OUI="002320"

LOGDIR=/var/log/openflow
PIDFILE=/var/run/$NAME.pid
DHCLIENT_PIDFILE=/var/run/dhclient.of0.pid
DODTIME=1                   # Time to wait for the server to die, in seconds
                            # If this value is set too low you might not
                            # let some servers to die gracefully and
                            # 'restart' will not work

# Include secchan defaults if available
unset NETDEVS
unset MODE
unset SWITCH_IP
unset CONTROLLER
unset PRIVKEY
unset CERT
unset CACERT
unset CACERT_MODE
unset MGMT_VCONNS
unset COMMANDS
unset DAEMON_OPTS
unset CORE_LIMIT
unset DATAPATH_ID
default=/etc/default/openflow-switch
if [ -f $default ] ; then
	. $default
fi

set -e

running_pid()
{
    # Check if a given process pid's cmdline matches a given name
    pid=$1
    name=$2
    [ -z "$pid" ] && return 1 
    [ ! -d /proc/$pid ] &&  return 1
    cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
    # Is this the expected child?
    case $cmd in
        $name|*/$name)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

running()
{
# Check if the process is running looking at /proc
# (works for all users)

    # No pidfile, probably no daemon present
    [ ! -f "$PIDFILE" ] && return 1
    # Obtain the pid and check it against the binary name
    pid=`cat $PIDFILE`
    running_pid $pid $NAME || return 1
    return 0
}

force_stop() {
# Forcefully kill the process
    [ ! -f "$PIDFILE" ] && return
    if running ; then
        kill -15 $pid
        # Is it really dead?
        [ -n "$DODTIME" ] && sleep "$DODTIME"s
        if running ; then
            kill -9 $pid
            [ -n "$DODTIME" ] && sleep "$DODTIME"s
            if running ; then
                echo "Cannot kill $NAME (pid=$pid)!"
                exit 1
            fi
        fi
    fi
    rm -f $PIDFILE
    return 0
}

must_succeed() {
    echo -n "$1: "
    shift
    if "$@"; then
        echo "success."
    else
        echo " ERROR."
        exit 1
    fi
}

check_op() {
    echo -n "$1: "
    shift
    if "$@"; then
        echo "success."
    else
        echo " ERROR."
    fi
}

configure_ssl() {
    if (test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap) \
       || test ! -e "$PRIVKEY" || test ! -e "$CERT" \
       || (test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap); then
        if test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap
        then
            echo "CACERT_MODE is not set to 'secure' or 'bootstrap'"
        fi
        if test ! -e "$PRIVKEY"; then
            echo "$PRIVKEY: private key missing" >&2
        fi
        if test ! -e "$CERT"; then
            echo "$CERT: certificate for private key missing" >&2
        fi
        if test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap; then
            echo "$CACERT: CA certificate missing (and CA certificate bootstrapping not enabled)" >&2
        fi
        echo "Run ofp-switch-setup (in the openflow-switch-config package) or edit /etc/default/openflow-switch to configure" >&2
        if test "$MODE" = discovery; then
            echo "You may also delete or rename $PRIVKEY to disable SSL requirement" >&2
        fi
        exit 1
    fi

    SSL_OPTS="--private-key=$PRIVKEY --certificate=$CERT"
    if test ! -e "$CACERT" && test "$CACERT_MODE" = bootstrap; then
        SSL_OPTS="$SSL_OPTS --bootstrap-ca-cert=$CACERT"
    else
        SSL_OPTS="$SSL_OPTS --ca-cert=$CACERT"
    fi
}

check_int_var() {
    eval value=\$$1
    if test -n "$value"; then
        if expr "X$value" : 'X[0-9][0-9]*$'; then
            if test $value -lt $2; then
                echo "warning: The $1 option may not be set to a value below $2, treating as $2" >&2
                eval $1=$2
            fi
        else
            echo "warning: The $1 option must be set to a number, ignoring" >&2
            unset $1
        fi
    fi
}

check_new_option() {
    case $DAEMON_OPTS in
        *$1*)
            echo "warning: The $1 option in DAEMON_OPTS may now be set with the $2 variable in $default.  The setting in DAEMON_OPTS will override the $2 variable, which will prevent the switch UI from configuring $1." >&2
            ;;
    esac
}

case "$1" in
    start)
        if test -z "$NETDEVS"; then
            echo "$default: No network devices configured, switch disabled" >&2
            echo "Run ofp-switch-setup (in the openflow-switch-config package) or edit /etc/default/openflow-switch to configure" >&2
            exit 0
        fi
        if test "$MODE" = discovery; then
            unset CONTROLLER
        elif test "$MODE" = in-band || test "$MODE" = out-of-band; then
            if test -z "$CONTROLLER"; then
                echo "$default: No controller configured and not configured for discovery, switch disabled" >&2
                echo "Run ofp-switch-setup (in the openflow-switch-config package) or edit /etc/default/openflow-switch to configure" >&2
                exit 0
            fi
        else
            echo "$default: MODE must set to 'discovery', 'in-band', or 'out-of-band'" >&2
            echo "Run ofp-switch-setup (in the openflow-switch-config package) or edit /etc/default/openflow-switch to configure" >&2
            exit 1
        fi
        : ${PRIVKEY:=/etc/openflow-switch/of0-privkey.pem}
        : ${CERT:=/etc/openflow-switch/of0-cert.pem}
        : ${CACERT:=/etc/openflow-switch/cacert.pem}
        case $CONTROLLER in
            '')
                # Discovery mode.
                if test -e "$PRIVKEY"; then
                    configure_ssl
                fi
                ;;
            tcp:*)
                ;;
            ssl:*)
                configure_ssl
                ;;
            *)
                echo "$default: CONTROLLER must be in the form 'ssl:HOST[:PORT]' or 'tcp:HOST[:PORT]' when not in discovery mode" >&2
                echo "Run ofp-switch-setup (in the openflow-switch-config package) or edit /etc/default/openflow-switch to configure" >&2
                exit 1
        esac
        case $DISCONNECTED_MODE in
            ''|switch|drop) ;; 
            *) echo "$default: warning: DISCONNECTED_MODE is not 'switch' or 'drop'" >&2 ;;
        esac

        check_int_var RATE_LIMIT 100
        check_int_var INACTIVITY_PROBE 5
        check_int_var MAX_BACKOFF 1

        check_new_option --fail DISCONNECTED_MODE
        check_new_option --stp STP
        check_new_option --rate-limit RATE_LIMIT
        check_new_option --inactivity INACTIVITY_PROBE
        check_new_option --max-backoff MAX_BACKOFF
        case $DAEMON_OPTS in
            *--rate-limit*)
                echo "$default: --rate-limit may now be set with RATE_LIMIT" >&2
        esac

        echo -n "Loading openflow_mod: "
        if grep -q '^openflow_mod$' /proc/modules; then
            echo "already loaded, nothing to do."
        elif modprobe openflow_mod; then
            echo "success."
        else
            echo "ERROR."
            echo "openflow_mod has probably not been built for this kernel."
            if ! test -d /usr/share/doc/openflow-datapath-source; then
                echo "Install the openflow-datapath-source package, then read"
                echo "/usr/share/doc/openflow-datapath-source/README.Debian"
            else
                echo "For instructions, read"
                echo "/usr/share/doc/openflow-datapath-source/README.Debian"
            fi
            exit 1
        fi

        for netdev in $NETDEVS; do
            check_op "Removing IP address from $netdev" ifconfig $netdev 0.0.0.0
        done

        must_succeed "Adding datapath" dpctl adddp nl:0
        for netdev in $NETDEVS; do
            must_succeed "Adding $netdev to datapath" dpctl addif nl:0 $netdev
        done

        xx='[0-9abcdefABCDEF][0-9abcdefABCDEF]'
        case $DATAPATH_ID in
            '')
                # Check if the DMI System UUID contains a Nicira mac address
                # that should be used for this datapath.  The UUID is assumed 
                # to be RFC 4122 compliant.
                DMIDECODE=`which dmidecode`
                if [ -n $DMIDECODE ]; then
                    UUID_MAC=`$DMIDECODE -s system-uuid | cut -d'-' -f 5`
                    case $UUID_MAC in
                        $NICIRA_OUI*)
                            ifconfig of0 down
                            must_succeed "Setting of0 MAC address to $UUID_MAC" ifconfig of0 hw ether $UUID_MAC
                            ifconfig of0 up
                            ;;
                    esac
                fi  
                ;;
            $xx:$xx:$xx:$xx:$xx:$xx)
                ifconfig of0 down
                must_succeed "Setting of0 MAC address to $DATAPATH_ID" ifconfig of0 hw ether $DATAPATH_ID
                ifconfig of0 up
                ;;
            *)
                echo "DATAPATH_ID is not a valid MAC address in the form XX:XX:XX:XX:XX:XX, ignoring" >&2
                ;;
        esac

        if test "$MODE" = in-band; then
            if test "$SWITCH_IP" = dhcp; then
                must_succeed "Temporarily disabling of0" ifconfig of0 down
            else
                COMMAND="ifconfig of0 $SWITCH_IP"
                if test -n "$SWITCH_NETMASK"; then
                    COMMAND="$COMMAND netmask $SWITCH_NETMASK"
                fi
                must_succeed "Configuring of0: $COMMAND" $COMMAND
                if test -n "$SWITCH_GATEWAY"; then
                    # This can fail because the route already exists,
                    # so we don't insist that it succeed.
                    COMMAND="route add default gw $SWITCH_GATEWAY"
                    check_op "Adding default route: $COMMAND" $COMMAND
                fi
            fi
        else
            must_succeed "Disabling of0" ifconfig of0 down
        fi

        if test -n "$CORE_LIMIT"; then
            check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT"
        fi

        # Compose secchan options.
        set --
        set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err
        set -- "$@" --log-file
        set -- "$@" --detach --pidfile=$PIDFILE
        for vconn in $MGMT_VCONNS; do
            set -- "$@" --listen="$vconn"
        done
        if test -n "$MONITOR_VCONN"; then
            set -- "$@" --monitor="$MONITOR_VCONN"
        fi
        if test -n "$COMMANDS"; then
            set -- "$@" --command-acl="$COMMANDS"
        fi
        case $STP in
            yes) set -- "$@" --stp ;;
            no) set -- "$@" --no-stp ;;
        esac
        case $DISCONNECTED_MODE in
            switch) set -- "$@" --fail=open ;;
            drop) set -- "$@" --fail=closed ;;
        esac
        if test -n "$RATE_LIMIT"; then
            set -- "$@" --rate-limit=$RATE_LIMIT
        fi
        if test -n "$INACTIVITY_PROBE"; then
            set -- "$@" --inactivity-probe=$INACTIVITY_PROBE
        fi
        if test -n "$MAX_BACKOFF"; then
            set -- "$@" --max-backoff=$MAX_BACKOFF
        fi
        set -- "$@" $SSL_OPTS $DAEMON_OPTS
        if test "$MODE" = out-of-band; then
            set -- "$@" --out-of-band
        fi
        set -- "$@" nl:0 "$CONTROLLER"
	echo -n "Starting $DESC: "
	start-stop-daemon --start --quiet --pidfile $PIDFILE \
	    --exec $DAEMON -- "$@"
        if running; then
            echo "$NAME."
        else
            echo " ERROR."
        fi

        if test "$MODE" = in-band && test "$SWITCH_IP" = dhcp; then
            echo -n "Starting dhclient on of0: "
	    start-stop-daemon --start --quiet --pidfile $DHCLIENT_PIDFILE \
		--exec /sbin/dhclient -- -q -pf $DHCLIENT_PIDFILE of0
            if running; then
                echo "dhclient."
            else
                echo " ERROR."
            fi
        fi
	;;
    stop)
        if test -e /var/run/dhclient.of0.pid; then
	    echo -n "Stopping dhclient on of0: "
	    start-stop-daemon --stop --quiet --oknodo \
                --pidfile $DHCLIENT_PIDFILE --exec /sbin/dhclient
	    echo "dhclient."
        fi            

	echo -n "Stopping $DESC: "
	start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE \
	    --exec $DAEMON
	echo "$NAME."

        for netdev in $NETDEVS; do
            check_op "Removing $netdev from datapath" dpctl delif nl:0 $netdev
        done
        check_op "Deleting datapath" dpctl deldp nl:0
        check_op "Unloading kernel module" modprobe -r openflow_mod
	;;
    force-stop)
	echo -n "Forcefully stopping $DESC: "
        force_stop
        if ! running; then
            echo "$NAME."
        else
            echo " ERROR."
        fi
	;;
    reload)
        ;;
    force-reload)
	start-stop-daemon --stop --test --quiet --pidfile \
	    $PIDFILE --exec $DAEMON \
	    && $0 restart \
	    || exit 0
	;;
    restart)
        $0 stop || true
        $0 start
	;;
    status)
        echo -n "$NAME is "
        if running ;  then
            echo "running"
        else
            echo " not running."
            exit 1
        fi
        ;;
    *)
	N=/etc/init.d/$NAME
	echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2
	exit 1
	;;
esac

exit 0
